//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================

#include <mantle.h>

PPage * PPage::Clone()
{
    PAGE_ASSERT(this);

    PPage *pNewPage = new PPage;

    if (!pNewPage) {
        return NULL;
    }
    pNewPage->m_uAddr = ::DzAllocPages(m_uOrder, bsp.uUserPreferredZoneNo);
    if (!IS_VALID_PAGE_ADDRESS(pNewPage->m_uAddr)) {
        delete pNewPage;
        return NULL;
    }

    pNewPage->m_uOrder = m_uOrder;
    pNewPage->m_uSize = m_uSize;
    pNewPage->m_uOffset = m_uOffset;
    memcpy(__VA(pNewPage->m_uAddr), __VA(m_uAddr), m_uSize);

    return pNewPage;
}

void PageContainer::FreeAllPages()
{
    PPage * pPage;

    pPage = (PPage *)(m_pagesList.m_pNext);
    while ((DLinkNode *)pPage != &m_pagesList) {
        PAGE_ASSERT(pPage);

        pPage->Detach();
        DzFreePages(pPage->m_uAddr, pPage->m_uOrder);
        delete pPage;
        pPage = (PPage *)m_pagesList.m_pNext;
    }
}

PageContainer * PageContainer::Clone()
{
    PPage *pPage, *pNewPage;

    PageContainer *pNewPageContainer = new PageContainer;

    if (!pNewPageContainer) {
        return NULL;
    }

    ForEachDLinkNode(PPage *, pPage, &m_pagesList) {
        pNewPage = pPage->Clone();
        if (!pNewPage) {
            delete pNewPageContainer;
            return NULL;
        }
        pNewPageContainer->m_pagesList.InsertPrev(pNewPage);
    }
    return pNewPageContainer;
}

PPage * PageContainer::CreatePage(UInt32 uOffset, UInt32 uOrder)
{
    PPage * pNewPage;

    pNewPage = new PPage;
    if (!pNewPage) {
        return NULL;
    }

    pNewPage->m_uAddr = ::DzAllocPages(uOrder, bsp.uUserPreferredZoneNo);
    if (!IS_VALID_PAGE_ADDRESS(pNewPage->m_uAddr)) {
        delete pNewPage;
        return NULL;
    }

    pNewPage->m_uSize = 1 << (uOrder + PAGE_SHIFT);
    pNewPage->m_uOrder = uOrder;
    pNewPage->m_uOffset = uOffset;
    PutPage(pNewPage);

    return pNewPage;
}

#define MAX_PAGE_SHIFT (21)
#define MAX_PAGE_SIZE  (1u << MAX_PAGE_SHIFT)
#define MAX_PAGE_MASK  (~(MAX_PAGE_SIZE - 1))

ECode PageContainer::NestedAllocPages(
    PPage * const pInsertPos, UInt32 uNum,
    UInt32 uOrder, UInt32 uNewPageOffset)
{
    PPage *pNewPage;

    for (; uNum > 0 ; uNum--) {
        pNewPage = new PPage;

        if (pNewPage == NULL) {
            // Free alloced pages at PageContainer::AllocPages()
            return E_OUT_OF_MEMORY;
        }

        pNewPage->m_uAddr = ::DzAllocPages(uOrder, bsp.uUserPreferredZoneNo);
        if (!IS_VALID_PAGE_ADDRESS(pNewPage->m_uAddr)) {
            delete pNewPage;
            if (uOrder > 0) {
                uNum = uNum * 2;
                uOrder--;
                // If out of memory, free alloced pages at
                // PageContainer::AllocPages()
                return  NestedAllocPages(pInsertPos, uNum,
                                         uOrder, uNewPageOffset);
            }
            else {
                // Free alloced pages at PageContainer::AllocPages()
                return E_OUT_OF_MEMORY;
            }
        }
        else {
            pNewPage->m_uSize = 1 << (uOrder + PAGE_SHIFT);
            pNewPage->m_uOrder = uOrder;
            pNewPage->m_uOffset = uNewPageOffset;
            pInsertPos->InsertPrev(pNewPage);
            uNewPageOffset += pNewPage->m_uSize;
        }
    }
    return NOERROR;
}

ECode PageContainer::AllocPages(UInt32 uOffset, UInt32 cPages)
{
    PPage  *pNewPage, *pInsertPos;
    UInt32  n, uSize, uNewPageOffset;
    ECode ec;

    assert(PAGE_OFFSET(uOffset) == 0);

    uSize = P_TO_B(cPages);
    assert((Int32)uSize > 0);

    // Find insert position
    ForEachDLinkNode(PPage *, pInsertPos, &m_pagesList) {
        if (uOffset < pInsertPos->m_uOffset) {
            assert(uOffset + uSize <= pInsertPos->m_uOffset);
            assert(pInsertPos->m_pPrev == &m_pagesList \
                   || uOffset >= ((PPage*)pInsertPos->m_pPrev)->m_uOffset \
                   + ((PPage*)pInsertPos->m_pPrev)->m_uSize);
            break;
        }
    }

    uNewPageOffset = uOffset;
    for (n = PAGE_SHIFT; n < MAX_PAGE_SHIFT; n++) {
        if ((uSize >> n) & 1) {
            pNewPage = new PPage;
            if (pNewPage == NULL) {
                goto OutOfMemory;
            }

            pNewPage->m_uAddr = ::DzAllocPages(n - PAGE_SHIFT,
                bsp.uUserPreferredZoneNo);
            if (!IS_VALID_PAGE_ADDRESS(pNewPage->m_uAddr)) {
                delete pNewPage;
                if (n - PAGE_SHIFT > 0) {
                    ec = NestedAllocPages(pInsertPos, 2 ,
                                        n - PAGE_SHIFT - 1,
                                        uNewPageOffset);
                    if (FAILED(ec)) {
                       goto OutOfMemory;
                    }
                    uNewPageOffset += 1 << n;
                    continue;
                }
                else {
                    goto OutOfMemory;
                }
            }

            pNewPage->m_uSize = 1 << n;
            pNewPage->m_uOrder = n - PAGE_SHIFT;
            pNewPage->m_uOffset = uNewPageOffset;
            pInsertPos->InsertPrev(pNewPage);
            uNewPageOffset += pNewPage->m_uSize;
        }
    }

    if (uSize & MAX_PAGE_MASK) {
        n = uSize >> MAX_PAGE_SHIFT;
        ec = NestedAllocPages(pInsertPos, n,
                                MAX_PAGE_SHIFT - PAGE_SHIFT,uNewPageOffset);
        if (NOERROR != ec) {
            goto OutOfMemory;
        }
    }
    return NOERROR;

OutOfMemory:
    while ((&m_pagesList != pInsertPos->m_pPrev)
        && (uOffset <= ((PPage*)pInsertPos->m_pPrev)->m_uOffset)) {
        pNewPage = (PPage*)pInsertPos->m_pPrev;
        pNewPage->Detach();
        ::DzFreePages(pNewPage->m_uAddr, pNewPage->m_uOrder);
        delete pNewPage;
    }
    return E_OUT_OF_MEMORY;
}

void PageContainer::PutPage(PPage * pPage)
{
    PAGE_ASSERT(pPage);

    PPage * p;

    ForEachDLinkNode(PPage *, p, &m_pagesList) {
        if (pPage->m_uOffset < p->m_uOffset) {
            p->InsertPrev(pPage);
            assert(pPage->m_uOffset + pPage->m_uSize <= p->m_uOffset);
            assert(pPage->m_pPrev == &m_pagesList \
                   || pPage->m_uOffset >= ((PPage*)pPage->m_pPrev)->m_uOffset \
                   + ((PPage*)pPage->m_pPrev)->m_uSize);
            return;
        }
    }
    m_pagesList.InsertPrev(pPage);
}

ECode PageContainer::QueryPhysicalBlock(
    UInt32 uOffset, UInt32 *puPAddr, MemorySize *pSize)
{
    UInt32 uPAddr;
    MemorySize nSize;
    PPage *pPage = NULL, *p, *q;

    ForEachDLinkNode(PPage *, p, &m_pagesList) {
        if (FALL_INTO(uOffset, p->m_uOffset, p->m_uSize)) {
            pPage = p;
            break;
        }
    }

    if (NULL == pPage) {
        return E_DOES_NOT_EXIST;
    }

    uPAddr = pPage->m_uAddr;
    nSize  = pPage->m_uSize;

    p = (PPage *)pPage->Prev();
    q = pPage;
    while ((p != (PPage *)&m_pagesList)
            && (p->m_uOffset + p->m_uSize == q->m_uOffset)
            && (p->m_uAddr + p->m_uSize == q->m_uAddr)) {
        uPAddr = p->m_uAddr;
        nSize += p->m_uSize;
        q = p;
        p = (PPage *)p->Prev();
    }

    p = pPage;
    q = (PPage *)pPage->Next();
    while ((q != (PPage *)&m_pagesList)
            && (p->m_uOffset + p->m_uSize == q->m_uOffset)
            && (p->m_uAddr + p->m_uSize == q->m_uAddr)) {
        nSize += q->m_uSize;
        p = q;
        q = (PPage *)q->Next();
    }

    assert(IsPhysicalMemory(uPAddr, nSize));

    if (NULL != puPAddr) *puPAddr = uPAddr;
    if (NULL != pSize) *pSize = nSize;

    return NOERROR;
}
